// Written by Syranide, me@syranide.com

if SERVER then AddCSLuaFile("client.lua") return end

-- mode for showing grid only on USE, same for cursor
-- mode for showing one snap point only? (connect with above)
-- see if it possible to fix the wobbling when moving (doesn't wobble when you move normally)

local player
local target =        { active = false }
local snaptarget =    { active = false }
local snapkey =       false
local snaptime =      false
local snaplock =      false
local snapcursor =    false
local snapspawnmenu = false

local cache = {
	vPlayerPos = 0,
	vLookPos = 0,
	vLookClipPos = 0,
	vLookVector = 0,
}

local convars = {
	enabled      = 1,
	hidegrid     = 0,
	toggledelay  = 0,
	disableuse   = 0,
	allentities  = 0,
	alltools     = 0,
	enabletoggle = 0,
	lockdelay    = 0.5,
	distance     = 250,
	gridlimit    = 16,
	gridsize     = 8,
	gridalpha    = 0.4,
	gridoffset   = 0.5,
	boundingbox  = 1,
	revertaim    = 1,
	centerline   = 1,
}

local modelsaveset = {}
local modeloffsets = {}

---------

local function ToScreen(vWorld)
	local vsScreen = vWorld:ToScreen()
	return Vector(vsScreen.x, vsScreen.y, 0)
end

local function PointToScreen(vPoint)
	if cache.vLookVector:DotProduct(vPoint - cache.vLookClipPos) > 0 then
		return ToScreen(vPoint)
	end
end

local function LineToScreen(vStart, vEnd)
	local dotStart = cache.vLookVector:DotProduct(vStart - cache.vLookClipPos)
	local dotEnd = cache.vLookVector:DotProduct(vEnd - cache.vLookClipPos)
	
	if dotStart > 0 and dotEnd > 0 then
		return ToScreen(vStart), ToScreen(vEnd)
	elseif dotStart > 0 or dotEnd > 0 then
		local vLength = vEnd - vStart
		local vIntersect = vStart + vLength * ((cache.vLookClipPos:DotProduct(cache.vLookVector) - vStart:DotProduct(cache.vLookVector)) / vLength:DotProduct(cache.vLookVector))
		
		if dotStart <= 0 then
			return ToScreen(vIntersect), ToScreen(vEnd)
		else
			return ToScreen(vStart), ToScreen(vIntersect)
		end
	end
end

local function DrawSurfaceLine(vsA, vsB)
	surface.DrawLine(vsA.x, vsA.y, vsB.x, vsB.y)
end

-- origin (ray), vector (ray), point (plane), vector1?, vector2?
local function PlaneRayIntersect(o, v, p, e1, e2)
	local vp = v:Cross(e2)

	local d = e1:DotProduct(vp)
	if (d <= 0.0) then return end

	local vt = o - p
	local u = vt:DotProduct(vp)
	if (u < 0.0 or u > d) then return end

	local v = v:DotProduct(vt:Cross(e1))
	if (v < 0.0 or v > d) then return end

	local id = 1.0 / d
	return Vector(u * id, v * id, 0)
end

------------

local function OnInitialize()
	local data = file.Read('smartsnap_settings.txt')
	if data then
		lines = string.Explode("\n", data)
		header = table.remove(lines, 1)
		if header == 'SMARTSNAP_SETTINGS' then
			for _,line in ipairs(lines) do
				local pos = string.find(line, '=')
				if pos then
					 local key = string.Trim(string.sub(line, 1, pos - 1))
					 local value = string.Trim(string.sub(line, pos + 1))
					 
					 if convars[key] then convars[key] = value end
				end
			end
		end
	end
	
	for key,value in pairs(convars) do
		CreateClientConVar('snap_' .. key, value, true, false)
	end

	for _,filename in ipairs({'smartsnap_default_offsets.txt', 'smartsnap_custom_offsets.txt'}) do
		local file = file.Read(filename)
		if file then
			lines = string.Explode("\n", file)
			header = table.remove(lines, 1)
			if header == "SMARTSNAP_OFFSETS" then
				for _,line in ipairs(lines) do
					local pos = string.find(line, '=')
					if pos then
						 local key = string.Trim(string.sub(line, 1, pos - 1))
						 local value = string.Trim(string.sub(line, pos + 1))
						 
						 local c = string.Explode(",", value)
						 modeloffsets[key]= { tonumber(c[1]), tonumber(c[2]), tonumber(c[3]), tonumber(c[4]),  tonumber(c[5]),  tonumber(c[6])  }
					end
				end
			end
		end
	end
end
	
local function OnShutDown()
	output = "SMARTSNAP_SETTINGS\n"
	
	for key,_ in pairs(convars) do
		output = output .. key .. '=' .. GetConVarString('snap_' .. key) .. "\n"
	end
	
	file.Write('smartsnap_settings.txt', output)
	
	output = file.Read('smartsnap_custom_offsets.txt')
	if output == nil then
		output = "SMARTSNAP_OFFSETS\n"
	end
	
	-- perhaps it should re-save them all here instead?
	
	for model,_ in pairs(modelsaveset) do
		output = output .. model .. '=' .. table.concat(modeloffsets[model], ",") .. "\n"
	end
	
	file.Write('smartsnap_custom_offsets.txt', output)
end

local function VEX()
	if modeloffsets[target.entity:GetModel()] == nil then
		modeloffsets[target.entity:GetModel()] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }
	end
	return modeloffsets[target.entity:GetModel()]
end

concommand.Add("snap_dev_gridoffset", function(player, command, arguments) if target.active == true then if #arguments >= 1 then local v = VEX() v[target.face] = v[target.face] + tonumber(arguments[1]) end end end)
concommand.Add("snap_dev_saveoffset", function(player, command, arguments) if target.active == true then local v = VEX() modelsaveset[target.entity:GetModel()] = true end end)

local function SnapToggleGrid()
	if (GetConVarNumber("snap_enabled") == 0) then
		player:ConCommand('snap_enabled 1')
	else
		player:ConCommand('snap_enabled 0')
	end
end

local function SnapPress()
	if (snaplock or snapcursor) then
		snaptime = false
	else
		local toggledelay = GetConVarNumber("snap_toggledelay")
		if (toggledelay > 0 and snaptime and snaptime + toggledelay > CurTime()) then
			SnapToggleGrid()
			snaptime = false
			snaplock = false
		else
			snaptime = CurTime()
		end
	end
	
	snapkey = target.active
	
	if (!snapcursor) then
		snaplock = false
	end
end

local function SnapRelease()
	snapkey = false
end

local function SnapLock()
	snaplock = !snaplock
end

local function OnSpawnMenu()
	snapspawnmenu = true
end

local function OnKeyPress(player, key)
	if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then
		SnapPress()
	end
end

local function OnKeyRelease(player, key)
	if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then
		SnapRelease()
	end
end

local function OnThink()
	if (vgui.CursorVisible()) then
		if (!snapcursor and snaplock) then
			snaptarget = table.Copy(target)
		end
		
		snaptime = false
		snapcursor = true
	else
		if (snapcursor and snaplock) then
			target = snaptarget
		end
		
		snapspawnmenu = false
		snapcursor = false
	end
	
	if (GetConVarNumber("snap_enabletoggle") != 0) then
		if (snapkey and snaptime and !snaplock) then
			if (CurTime() > snaptime + GetConVarNumber("snap_lockdelay")) then
				snaplock = true
				snaptime = false
			end
		end
	end
		
	local locked = target.locked and target.active
	target.locked = (snapkey or snaplock and !snapcursor) and target.active
	
	if (!target.locked and locked and GetConVarNumber("snap_revertaim") != 0) then
		if (snapcursor) then
			local screen = target.entity:LocalToWorld(target.vector):ToScreen()
			gui.SetMousePos(math.Round(screen.x), math.Round(screen.y))
		else
			local angles = (target.entity:LocalToWorld(target.vector) - player:GetShootPos()):Angle()
			player:SetEyeAngles(angles)
		end
	end
end



local function CalculateSnap(X, Y, v)
	-- this needs to be cleaned up and cached somewhere?
	local lengthX = X:Length()
	local lengthY = Y:Length()
	local offsetX = GetConVarNumber("snap_gridoffset") / lengthX
	local offsetY = GetConVarNumber("snap_gridoffset") / lengthY
	local scaleX = 1 - offsetX * 2
	local scaleY = 1 - offsetY * 2
	local gridX = math.Clamp(math.floor(lengthX / (2 * GetConVarNumber("snap_gridsize")) * scaleX) * 2, 2, GetConVarNumber("snap_gridlimit"))
	local gridY = math.Clamp(math.floor(lengthY / (2 * GetConVarNumber("snap_gridsize")) * scaleY) * 2, 2, GetConVarNumber("snap_gridlimit"))
	
	BX = math.Clamp(math.Round((v.x - offsetX) / scaleX * gridX), 0, gridX)
	BY = math.Clamp(math.Round((v.y - offsetY) / scaleY * gridY), 0, gridY)
	
	return X * (offsetX + (BX / gridX) * scaleX) +
	       Y * (offsetY + (BY / gridY) * scaleY)
end

local function DrawGridLines(vOrigin, vSX, vSY, gridLines, offsetX, scaleX, offsetY, scaleY, sign)
	local centerline = GetConVarNumber("snap_centerline") != 0

	local vTemp = vOrigin + vSX * (offsetX + 0.5 * scaleX)
	local vX = vTemp + vSY * (offsetY)
	local vY = vTemp + vSY * (offsetY + scaleY)

	local vOffset, temp
	local vsNormal = (ToScreen(vX) - ToScreen(vY)):Normalize()
	
	if math.abs(vsNormal.x) < 1 - math.abs(vsNormal.y) then temp = -0.5 * sign else temp = 0.5 * sign end
	if math.abs(vsNormal.x) <     math.abs(vsNormal.y) then vsOffset = Vector(temp, 0, 0) else vsOffset = Vector(0, temp, 0) end

	for i = 0, gridLines do
		local vTemp = vOrigin + vSX * (offsetX + (i / gridLines) * scaleX)
		local vX = vTemp + vSY * (offsetY)
		local vY = vTemp + vSY * (offsetY + scaleY)
		
		local vsX, vsY = LineToScreen(vX, vY)
		
		if (vsX) then
			if (gridLines / i == 2 && centerline) then
				DrawSurfaceLine(vsX + vsOffset * -1, vsY + vsOffset * -1)
				DrawSurfaceLine(vsX + vsOffset *  3, vsY + vsOffset *  3)
			else
				DrawSurfaceLine(vsX + vsOffset,      vsY + vsOffset)
			end
		end
	end
end

local function DrawGrid(vOrigin, vSX, vSY)
	-- this needs to be cleaned up and cached somewhere?
	local lengthX = vSX:Length()
	local lengthY = vSY:Length()
	local offsetX = GetConVarNumber("snap_gridoffset") / lengthX
	local offsetY = GetConVarNumber("snap_gridoffset") / lengthY
	local scaleX = 1 - offsetX * 2
	local scaleY = 1 - offsetY * 2
	local gridX = math.Clamp(math.floor(lengthX / (2 * GetConVarNumber("snap_gridsize")) * scaleX) * 2, 2, GetConVarNumber("snap_gridlimit"))
	local gridY = math.Clamp(math.floor(lengthY / (2 * GetConVarNumber("snap_gridsize")) * scaleY) * 2, 2, GetConVarNumber("snap_gridlimit"))
	
	surface.SetDrawColor(0, 0, 0, math.Round(GetConVarNumber("snap_gridalpha") * 255))
	DrawGridLines(vOrigin, vSX, vSY, gridX, offsetX, scaleX, offsetY, scaleY,  1)
	DrawGridLines(vOrigin, vSY, vSX, gridY, offsetY, scaleY, offsetX, scaleX,  1)
	
	surface.SetDrawColor(255, 255, 255, math.Round(GetConVarNumber("snap_gridalpha") * 255))
	DrawGridLines(vOrigin, vSX, vSY, gridX, offsetX, scaleX, offsetY, scaleY, -1)
	DrawGridLines(vOrigin, vSY, vSX, gridY, offsetY, scaleY, offsetX, scaleX, -1)
end

-- vDirection is more like vTo
local function DrawBoundaryLines(vOrigin, vOpposite)
	local vPoint
	
	if (vOrigin:Distance(vOpposite) > 5) then
		vPoint = vOrigin + (vOpposite - vOrigin):Normalize() * 5
	else
		vPoint = vOrigin + (vOpposite - vOrigin) / 2
	end
	
	local vsA, vsB = LineToScreen(vPoint, vOrigin)
	
	if (vsA) then
		surface.SetDrawColor(0, 0, 255, 192)
		DrawSurfaceLine(vsA, vsB)
	end
end

local function DrawBoundary(vOrigin, vX, vY, vZ)
	DrawBoundaryLines(vOrigin, vX)
	DrawBoundaryLines(vOrigin, vY)
	DrawBoundaryLines(vOrigin, vZ)
end

local function DrawSnapCross(vsCenter, r, g, b)
	surface.SetDrawColor(0, 0, 0, 255)
	DrawSurfaceLine(vsCenter + Vector(-2.5, -2.0), vsCenter + Vector( 2.5,  3.0))
	DrawSurfaceLine(vsCenter + Vector( 1.5, -2.0), vsCenter + Vector(-3.5,  3.0))
	
	surface.SetDrawColor(r, g, b, 255)
	DrawSurfaceLine(vsCenter + Vector(-1.5, -2.0), vsCenter + Vector( 3.5,  3.0))
	DrawSurfaceLine(vsCenter + Vector( 2.5, -2.0), vsCenter + Vector(-2.5,  3.0))
end




local function OnPaintHUD()
	player = LocalPlayer()

	target.active = false
	
	if (GetConVarNumber("snap_enabled") == 0) then return end	
	if (!player:Alive() or player:InVehicle()) then return end
	
	if (target.locked) then
		if (!target.entity:IsValid()) then return end
	else
		local trace = player:GetEyeTrace()
		cache.vLookTrace = trace
		if (!trace.HitNonWorld) then return end
		
		local entity = trace.Entity
		if (entity == nil) then return end
		
		if (entity:GetClass() != "prop_physics" and GetConVarNumber("snap_allentities") == 0) then return end
		if (!player:GetActiveWeapon():IsValid()) then return end
		if (player:GetActiveWeapon():GetClass() == "weapon_physgun") then return end
		if (player:GetActiveWeapon():GetClass() != "gmod_tool" and GetConVarNumber("snap_alltools") == 0) then return end
		
		target.entity = entity
	end
	
	-- updating the cache perhaps shouldn't be done here, CalcView?
	cache.vLookPos = player:GetShootPos()
	cache.vLookVector = player:GetCursorAimVector()
	cache.vLookClipPos = cache.vLookPos + cache.vLookVector * 8

	local offsets = modeloffsets[target.entity:GetModel()]
	if not offset then offsets = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 } end
	
	local obbmax = target.entity:OBBMaxs()
	local obbmin = target.entity:OBBMins()
	
	local obvsnap = {
		lsw = target.entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmin.z)),
		lse = target.entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmin.z)),
		lnw = target.entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmin.z)),
		lne = target.entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmin.z)),
		usw = target.entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmax.z)),
		use = target.entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmax.z)),
		unw = target.entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmax.z)),
		une = target.entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmax.z)),
	}
	
	local obbmax = obbmax - Vector(offsets[5], offsets[3], offsets[1])
	local obbmin = obbmin + Vector(offsets[6], offsets[4], offsets[2])
	
	local obvgrid = {
		lsw = target.entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmin.z)),
		lse = target.entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmin.z)),
		lnw = target.entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmin.z)),
		lne = target.entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmin.z)),
		usw = target.entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmax.z)),
		use = target.entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmax.z)),
		unw = target.entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmax.z)),
		une = target.entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmax.z)),
	}
	
	if (!target.locked) then
		-- should improve this by expanding the bounding box or something instead!
		-- create a larger bounding box and then planes for each side, and check distance from the plane
		-- separate function perhaps?
		local distance = (player:GetPos() - target.entity:GetPos()):Length() - (obvgrid.unw - obvgrid.lse):Length()
		if (distance > GetConVarNumber("snap_distance")) then return end
		
		local faces = {
			{ obvgrid.unw, obvgrid.usw - obvgrid.unw, obvgrid.une - obvgrid.unw },
			{ obvgrid.lsw, obvgrid.lnw - obvgrid.lsw, obvgrid.lse - obvgrid.lsw },
			{ obvgrid.unw, obvgrid.une - obvgrid.unw, obvgrid.lnw - obvgrid.unw },
			{ obvgrid.usw, obvgrid.lsw - obvgrid.usw, obvgrid.use - obvgrid.usw },
			{ obvgrid.une, obvgrid.use - obvgrid.une, obvgrid.lne - obvgrid.une },
			{ obvgrid.unw, obvgrid.lnw - obvgrid.unw, obvgrid.usw - obvgrid.unw },
		}
		
		for face,vertices in ipairs(faces) do
			intersection = PlaneRayIntersect(cache.vLookPos, cache.vLookVector, vertices[1], vertices[2], vertices[3])
			if (intersection) then
				target.face = face
				break
			end
		end
		
		if intersection == nil then return end
	end
	
	-- put into a separate function perhaps?
	if target.face == 1 then
		vectorOrigin = obvgrid.unw
		vectorX = obvgrid.usw - obvgrid.unw
		vectorY = obvgrid.une - obvgrid.unw
		vectorNormal = obvgrid.unw - obvgrid.lnw
		
		vectorLength = obvsnap.lnw - obvsnap.unw
		vectorOffset = Vector(0, 0, -offsets[1])
	elseif target.face == 2 then
		vectorOrigin = obvgrid.lsw
		vectorX = obvgrid.lnw - obvgrid.lsw
		vectorY = obvgrid.lse - obvgrid.lsw
		vectorNormal = obvgrid.lsw - obvgrid.usw
		
		vectorLength = obvsnap.usw - obvsnap.lsw
		vectorOffset = Vector(0, 0, offsets[2])
	elseif target.face == 3 then
		vectorOrigin = obvgrid.unw
		vectorX = obvgrid.une - obvgrid.unw
		vectorY = obvgrid.lnw - obvgrid.unw
		vectorNormal = obvgrid.unw - obvgrid.usw
		
		vectorLength = obvsnap.usw - obvsnap.unw
		vectorOffset = Vector(0, -offsets[3], 0)
	elseif target.face == 4 then
		vectorOrigin = obvgrid.usw
		vectorX = obvgrid.lsw - obvgrid.usw
		vectorY = obvgrid.use - obvgrid.usw
		vectorNormal = obvgrid.usw - obvgrid.unw
		
		vectorLength = obvsnap.unw - obvsnap.usw
		vectorOffset = Vector(0, offsets[4], 0)
	elseif target.face == 5 then
		vectorOrigin = obvgrid.une
		vectorX = obvgrid.use - obvgrid.une
		vectorY = obvgrid.lne - obvgrid.une
		vectorNormal = obvgrid.une - obvgrid.unw
		
		vectorLength = obvsnap.unw - obvsnap.une
		vectorOffset = Vector(-offsets[5], 0, 0)
	elseif target.face == 6 then
		vectorOrigin = obvgrid.unw
		vectorX = obvgrid.lnw - obvgrid.unw
		vectorY = obvgrid.usw - obvgrid.unw
		vectorNormal = obvgrid.unw - obvgrid.une
		
		vectorLength = obvsnap.une - obvsnap.unw
		vectorOffset = Vector(offsets[6], 0, 0)
	end
	
	vectorNormal:Normalize()
	
	-- separate function perhaps?
	if (GetConVarNumber("snap_boundingbox") != 0) then
		DrawBoundary(obvgrid.unw, obvgrid.lnw, obvgrid.usw, obvgrid.une)
		DrawBoundary(obvgrid.une, obvgrid.lne, obvgrid.use, obvgrid.unw)
		DrawBoundary(obvgrid.lnw, obvgrid.unw, obvgrid.lsw, obvgrid.lne)
		DrawBoundary(obvgrid.lne, obvgrid.une, obvgrid.lse, obvgrid.lnw)
		DrawBoundary(obvgrid.usw, obvgrid.lsw, obvgrid.unw, obvgrid.use)
		DrawBoundary(obvgrid.use, obvgrid.lse, obvgrid.une, obvgrid.usw)
		DrawBoundary(obvgrid.lsw, obvgrid.usw, obvgrid.lnw, obvgrid.lse)
		DrawBoundary(obvgrid.lse, obvgrid.use, obvgrid.lne, obvgrid.lsw)
	end
	
	local vectorGrid
	
	if (!target.locked) then
		-- why do I add vectorOrigin here and then remove the offset, why not use the SNAP ORIGIN instead?
		-- endpos is probably wrong too, doesn't accout for the offset I think
		vectorGrid = vectorOrigin + CalculateSnap(vectorX, vectorY, intersection)
		
		local trace = util.TraceLine({
			start  = target.entity:LocalToWorld(target.entity:WorldToLocal(vectorGrid) - vectorOffset) + vectorNormal * 0.01,
			endpos = vectorGrid + vectorLength,
		})
		
		vectorSnap = trace.HitPos
		target.offset = target.entity:WorldToLocal(vectorSnap)
		target.vector = target.entity:WorldToLocal(vectorGrid)
		
		target.error = true

		if (trace.Entity == nil or !trace.Entity:IsValid()) then
			snaperror = -1
		elseif (trace.Entity != target.entity) then
			snaperror = -2
		elseif (trace.HitPos == trace.StartPos) then
			snaperror = -2
		else
			snaperror = (player:GetEyeTrace().HitPos - trace.HitPos):Length()
			target.error = false
			
			if ((vectorSnap - vectorGrid):Length() > 0.5) then
				local marker = PointToScreen(vectorSnap)
				
				if (marker) then
					DrawSnapCross(marker, 255, 255, 255)
				end
			end
		end
	else
		vectorGrid = target.entity:LocalToWorld(target.vector)
		vectorSnap = target.entity:LocalToWorld(target.offset)
	
		snaperror = (player:GetEyeTrace().HitPos - vectorSnap):Length()
		
		local marker = PointToScreen(vectorSnap)
		
		if (marker) then
			if (target.error == true) then
				snaperror = -2
				DrawSnapCross(marker, 0, 255, 255)
			elseif (snaperror < 0.001) then
				DrawSnapCross(marker, 0, 255, 0)
			elseif (snaperror < 0.1) then
				DrawSnapCross(marker, 255, 255, 0)
			else
				DrawSnapCross(marker, 255, 0, 0)
			end
		end
	end
	
	if (GetConVarNumber("snap_hidegrid") == 0) then
		DrawGrid(vectorOrigin, vectorX, vectorY)
	end

	target.active = true
	
	local vsCursor = PointToScreen(vectorGrid)
	
	if (vsCursor) then
		if (snaperror == -1) then
			target.active = false
			DrawSnapCross(vsCursor, 0, 255, 255)
		elseif (snaperror == -2) then
			DrawSnapCross(vsCursor, 255, 0, 255)
		elseif (snaperror < 0.001) then
			DrawSnapCross(vsCursor, 0, 255, 0)
		elseif (snaperror < 0.1) then
			DrawSnapCross(vsCursor, 255, 255, 0)
		else
			DrawSnapCross(vsCursor, 255, 0, 0)
		end
	end
end

local function OnSnapView(player, origin, angles, fov)
	local targetvalid = target.active and target.locked and target.entity:IsValid()
	local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid()

	if (snapcursor and !snapspawnmenu and targetvalid) then
		local screen = ToScreen(target.entity:LocalToWorld(target.offset))
		gui.SetMousePos(math.Round(screen.x), math.Round(screen.y))
	end
	
	if (!snapcursor and targetvalid) then
		return {angles = (target.entity:LocalToWorld(target.offset) - player:GetShootPos()):Angle()}
	elseif (snaplock and snaptargetvalid) then	
		return {angles = (snaptarget.entity:LocalToWorld(snaptarget.offset) - player:GetShootPos()):Angle()}
	end
end

local function OnSnapAim(user)
	local targetvalid = target.active and target.locked and target.entity:IsValid()
	local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid()

	if (!snapcursor and targetvalid) then
		user:SetViewAngles((target.entity:LocalToWorld(target.offset) - player:GetShootPos()):Angle())
	elseif (snaplock and snaptargetvalid) then	
		user:SetViewAngles((snaptarget.entity:LocalToWorld(snaptarget.offset) - player:GetShootPos()):Angle())
	end
end

concommand.Add("+snap", SnapPress)
concommand.Add("-snap", SnapRelease)
concommand.Add("snaplock", SnapLock)
concommand.Add("snaptogglegrid", SnapToggleGrid)

hook.Add("Initialize", "SmartsnapInitialize", OnInitialize)

hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu)

hook.Add("Think", "SmartsnapThink", OnThink)
hook.Add("ShutDown", "SmartsnapShutDown", OnShutDown)

hook.Add("KeyPress", "SmartsnapKeyPress", OnKeyPress)
hook.Add("KeyRelease", "SmartsnapKeyRelease", OnKeyRelease)

hook.Add("CreateMove", "SmartsnapSnap", OnSnapAim)
hook.Add("CalcView", "SmartsnapSnapView", OnSnapView)

hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu)

hook.Add("HUDPaintBackground", "SmartsnapPaintHUD", OnPaintHUD)
